#include "dicomprintthread.h"
#include "global.h"
#include "dcmtk/dcmpstat/dviface.h"
#include "dcmtk/dcmpstat/dvpsprt.h"
#include "dcmtk/dcmpstat/dvpshlp.h"
#include "dcmtk/dcmpstat/dvpssp.h"
#include "dcmtk/dcmimgle/dcmimage.h"

#include <QDir>
#include <QTextStream>
#include <QPixmap>
#include <QPrinterInfo>
#include <QPainter>
#include <QSettings>

DicomPrintThread::DicomPrintThread(const QString &printerAE, QMutex *mutex, QObject *parent) :
    dvi(new DVInterface(DCMPSTAT_CFG)),
    _mutex(mutex),
    currentRule(0),
    aetitle(printerAE),
    QThread(parent)
{
}

DicomPrintThread::~DicomPrintThread()
{
    delete dvi;
}

void DicomPrintThread::run()
{
    QMutexLocker locker(_mutex);
    updateJobList();
    if (printJobList.size())
        spoolJobList();
    else
        emit threadMsg(LL_Info, tr("No print job found."));
}

void DicomPrintThread::spoolJobList()
{
    int filmCount = getFilmCount();
    int printed = 0;
    int jobCount = printJobList.size();
    PrintJob *currentJob;

    while (printJobList.size()) {
        emit threadMsg(LL_Info, tr("Printing, please wait..."));
        currentJob = printJobList.takeFirst();
        dvi->clearFilmSessionSettings();
        if (!currentJob->mediumType.isEmpty())
            dvi->setPrinterMediumType(currentJob->mediumType.toLatin1());
        if (!currentJob->filmDestination.isEmpty())
            dvi->setPrinterFilmDestination(currentJob->filmDestination.toLatin1());
        if (!currentJob->filmSessionLabel.isEmpty())
            dvi->setPrinterFilmSessionLabel(currentJob->filmSessionLabel.toLatin1());
        if (!currentJob->printPriority.isEmpty())
            dvi->setPrinterPriority(currentJob->printPriority.toLatin1());
        if (!currentJob->ownerID.isEmpty())
            dvi->setPrinterOwnerID(currentJob->ownerID.toLatin1());
        dvi->setPrinterNumberOfCopies(currentJob->numberOfCopies);

        if (filmCount>0) {
            if (spoolStoredPrintFile(currentJob->storedPrintFilename)) {
                printed += currentJob->numberOfCopies;
                filmCount -= currentJob->numberOfCopies;
                jobCount--;
            }
        } else {
            emit threadMsg(LL_Error, tr("Film exhausted, printing aborted, please add films."));
        }

        dvi->deleteInstance(currentJob->studyUID.toLatin1(),
                           currentJob->seriesUID.toLatin1(),
                           currentJob->instanceUID.toLatin1());
        dvi->releaseDatabase();
        delete currentJob;
    }

    if (printed) {
        QSettings settings;
        settings.setValue(FILM_USED, settings.value(FILM_USED).toULongLong()+printed);
        setFilmCount(filmCount);
        emit threadMsg(LL_Info, tr("%1 page(s) printed.").arg(printed));
    }
    if (jobCount) {
        emit threadMsg(LL_Warn, tr("%1 print job(s) failed.").arg(jobCount));
    }
}

bool DicomPrintThread::spoolStoredPrintFile(const QString &spfile)
{
    DcmFileFormat *ffile = NULL;
    DcmDataset *dset = NULL;
    bool ret = true;

    OFCondition result = DVPSHelper::loadFileFormat(spfile.toLatin1(), ffile);
    if (result.bad()) {
        emit threadMsg(LL_Error, tr("Load stored print file failed: %1.").arg(result.text()));
        delete ffile;
        return false;
    }
    dset = ffile->getDataset();
    DVPSStoredPrint &stprint = dvi->getPrintHandler();
    result = stprint.read(*dset);
    delete ffile;

    if (result.bad()) {
        emit threadMsg(LL_Error, tr("Read stored print dataset failed: %1.").arg(result.text()));
        return false;
    }

    QList<QPixmap> pixmapList;
    const char *studyUID = NULL;
    const char *seriesUID = NULL;
    const char *instanceUID = NULL;
    const char *imagefile = NULL;

    size_t numberOfImages = stprint.getNumberOfImages();
    for (size_t currentImage = 0; currentImage < numberOfImages; ++currentImage) {
        result = stprint.getImageReference(currentImage, studyUID, seriesUID, instanceUID);
        if (result.good() && studyUID && seriesUID && instanceUID) {
            imagefile = dvi->getFilename(studyUID, seriesUID, instanceUID);
            QPixmap pixmap;
            dcm2bmp(pixmap, imagefile);
            pixmapList.append(pixmap);
            dvi->deleteInstance(studyUID, seriesUID, instanceUID);
        }
    }

    setCurrentRule();
    ret = printFilm(stprint, pixmapList);

    dvi->deleteInstance(stprint.getStudyInstanceUID(),
                        stprint.getSeriesInstanceUID(),
                        stprint.getSOPInstanceUID());
    dvi->releaseDatabase();

    return ret;
}

void DicomPrintThread::setCurrentRule()
{
    if (ruleList.size()) {
        foreach (PrintRule *rule, ruleList) {
            if (rule->aetitle == aetitle) {
                currentRule = rule;
                break;
            }
        }
    }
}

bool DicomPrintThread::printFilm(DVPSStoredPrint &stprint, const QList<QPixmap> &pixmaps)
{
    QPrinterInfo info;
    if (!currentRule) {
        emit threadMsg(LL_Warn, tr("No printer set for rule title \"%1\", use default printer.").arg(aetitle));
    } else {
        info = QPrinterInfo::printerInfo(currentRule->printer);
        if (info.isNull()) {
            emit threadMsg(LL_Warn, tr("Invalid printer for rule title \"%1\", use default printer.").arg(aetitle));
        }
    }
    if (info.isNull()) {
        info = QPrinterInfo::defaultPrinter();
        if (info.isNull()) {
            emit threadMsg(LL_Error, tr("No available printer, abort print."));
            return false;
        }
    }
    QPrinter printer(info, QPrinter::HighResolution);
    //setPaperSize(&printer, QString::fromLatin1(stprint.getFilmSizeID()));
    printer.setResolution(QString::fromLatin1(stprint.getResolutionID())==QString::fromLatin1("HIGH")?800:400);
    printer.setOrientation(stprint.getFilmOrientation()==DVPSF_portrait?QPrinter::Portrait:QPrinter::Landscape);
    printer.setCopyCount(dvi->getPrinterNumberOfCopies());

    QPainter painter(&printer);
    drawPageHeaders(&painter);
    setImageArea(&painter, printer.resolution(),
                 stprint.getFilmOrientation()==DVPSF_portrait,
                 QString::fromLatin1(stprint.getFilmSizeID()));
    QList<QRect> imageRects;
    drawImageGrids(&painter, printer.resolution(),
                   stprint.getImageDisplayFormatColumns(),
                   stprint.getImageDisplayFormatRows(),
                   imageRects);
    drawImages(&painter, imageRects, pixmaps);

    return true;
}

void DicomPrintThread::setImageArea(QPainter *painter, int dpi, bool portrait, const QString &filmSize)
{
    double width = 0, height = 0;
    QString sizeId = filmSize;
    sizeId.remove("IN");
    QStringList size = sizeId.split(QChar('X'), QString::SkipEmptyParts);
    if (size.size() == 2) {
       width = size.at(0).toDouble();
       height = size.at(1).toDouble();
    }

    if (width == 0 || height == 0) {
        emit threadMsg(LL_Warn, tr("Invalid film size id, film size ignored."));
        return;
    }

    QRect viewRect = painter->viewport();
    QBrush borderBrush(Qt::SolidPattern);
    if (currentRule) {
        switch (currentRule->border) {
        case DT_Client:
            borderBrush.setStyle(Qt::NoBrush);
            break;
        case DT_Black:
            borderBrush.setColor(Qt::black);
            break;
        case DT_White:
            borderBrush.setColor(Qt::white);
            break;
        }
    }
    painter->fillRect(viewRect, borderBrush);

    if (currentRule && currentRule->trueSize) {
        viewRect.setWidth(width*dpi);
        viewRect.setHeight(height*dpi);
    } else {
        double filmRatio = width/height;
        if (!portrait) filmRatio = height/width;
        int temp = viewRect.width() - filmRatio*viewRect.height();
        if (temp > 0) {
            viewRect.setLeft(viewRect.left()+temp/2);
            viewRect.setRight(viewRect.right()-temp/2);
        } else {
            temp = viewRect.height() - viewRect.width()/filmRatio;
            viewRect.setTop(viewRect.top()+temp/2);
            viewRect.setBottom(viewRect.bottom()-temp/2);
        }
    }
    painter->setViewport(viewRect);
    painter->setWindow(viewRect);
}

void DicomPrintThread::drawPageHeaders(QPainter *painter)
{
    QFont oldFont = painter->font();
    QPen oldPen = painter->pen();
    QRect viewRect = painter->viewport();

    int headerHeight = 0;
    int footerHeight = 0;

    if (!(format.header.text.isEmpty() && format.header.icon.isNull())) {
        painter->setFont(format.header.font);
        painter->setPen(format.header.color);
        QRect textRect = painter->fontMetrics().boundingRect(format.header.text);
        headerHeight = textRect.height();
        if (headerHeight==0) headerHeight = painter->fontMetrics().height();
        QPixmap pixmap = format.header.icon.scaledToHeight(headerHeight);
        int xPos = 0;

        switch (format.header.align) {
        case AT_Left:
            xPos = viewRect.left();
            break;
        case AT_Center:
            xPos = viewRect.left() + (viewRect.width()-(pixmap.width()+textRect.width()))/2;
            break;
        case AT_Right:
            xPos = viewRect.right() - (pixmap.width()+textRect.width());
            break;
        }

        painter->drawPixmap(xPos, viewRect.top(), pixmap);
        painter->drawText(xPos+pixmap.width(), viewRect.top()+headerHeight-painter->fontMetrics().descent(), format.header.text);
    }

    if (!(format.footer.text.isEmpty() && format.footer.icon.isNull())) {
        painter->setFont(format.footer.font);
        painter->setPen(format.footer.color);
        QRect textRect = painter->fontMetrics().boundingRect(format.footer.text);
        footerHeight = textRect.height();
        if (footerHeight==0) footerHeight = painter->fontMetrics().height();
        QPixmap pixmap = format.footer.icon.scaledToHeight(footerHeight);

        int xPos = 0;
        switch (format.footer.align) {
        case AT_Left:
            xPos = viewRect.left();
            break;
        case AT_Center:
            xPos = viewRect.left() + (viewRect.width()-(pixmap.width()+textRect.width()))/2;
            break;
        case AT_Right:
            xPos = viewRect.right() - (pixmap.width()+textRect.width());
            break;
        }

        painter->drawPixmap(xPos, viewRect.bottom()-footerHeight, pixmap);
        painter->drawText(xPos+pixmap.width(), viewRect.bottom()-painter->fontMetrics().descent(), format.footer.text);
    }

    painter->setFont(oldFont);
    painter->setPen(oldPen);
    viewRect.adjust(0, headerHeight, 0, -footerHeight);
    painter->setViewport(viewRect);
    painter->setWindow(viewRect);
}

void DicomPrintThread::drawImageGrids(QPainter *painter, int dpi, int column, int row, QList<QRect> &rects)
{
    QRect viewRect = painter->viewport();
    if (column<1) column=1;
    if (row<1) row=1;
    int x = viewRect.width()/column;
    int y = viewRect.height()/row;

    for (int i = 0; i < row; ++i) {
        for (int j = 0; j < column; ++j) {
            rects << QRect(viewRect.left()+(x+int((format.xSpacing/25.4)*dpi))*j,
                           viewRect.top()+(y+int((format.ySpacing/25.4)*dpi))*i,
                           x, y);
        }
    }
}

void DicomPrintThread::drawImages(QPainter *painter, const QList<QRect> &rects, const QList<QPixmap> &pixmaps)
{
    QBrush emptyBrush(Qt::SolidPattern);
    if (currentRule) {
        switch (currentRule->emptyImage) {
        case DT_Client:
            emptyBrush.setStyle(Qt::NoBrush);
            break;
        case DT_Black:
            emptyBrush.setColor(Qt::black);
            break;
        case DT_White:
            emptyBrush.setColor(Qt::white);
            break;
        }
    }

    for (int i = 0; i < rects.size(); ++i) {
        QPixmap pixmap;
        if (i < pixmaps.size()) pixmap = pixmaps.at(i);
        if (pixmap.isNull()) {
            painter->fillRect(rects.at(i), emptyBrush);
        } else {
            painter->drawPixmap(rects.at(i), pixmap.scaled(rects.at(i).size(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
        }
    }
}

void DicomPrintThread::setPaperSize(QPrinter *printer, const QString &film)
{
    if (film == "8INX10IN") {
        if (paperMap.film8x10 == PS_Defined)
            printer->setPaperSize(QSizeF(paperMap.definedWidth, paperMap.definedHeight),
                                  QPrinter::Millimeter);
        else
            printer->setPaperSize(getPaperSize(paperMap.film8x10), QPrinter::Millimeter);
    } else if (film == "10INX12IN") {
        if (paperMap.film10x12 == PS_Defined)
            printer->setPaperSize(QSizeF(paperMap.definedWidth, paperMap.definedHeight),
                                  QPrinter::Millimeter);
        else
            printer->setPaperSize(getPaperSize(paperMap.film10x12), QPrinter::Millimeter);
    } else if (film == "10INX14IN") {
        if (paperMap.film10x14 == PS_Defined)
            printer->setPaperSize(QSizeF(paperMap.definedWidth, paperMap.definedHeight),
                                  QPrinter::Millimeter);
        else
            printer->setPaperSize(getPaperSize(paperMap.film10x14), QPrinter::Millimeter);
    } else if (film == "11INX14IN") {
        if (paperMap.film11x14 == PS_Defined)
            printer->setPaperSize(QSizeF(paperMap.definedWidth, paperMap.definedHeight),
                                  QPrinter::Millimeter);
        else
            printer->setPaperSize(getPaperSize(paperMap.film11x14), QPrinter::Millimeter);
    } else if (film == "14INX14IN") {
        if (paperMap.film14x14 == PS_Defined)
            printer->setPaperSize(QSizeF(paperMap.definedWidth, paperMap.definedHeight),
                                  QPrinter::Millimeter);
        else
            printer->setPaperSize(getPaperSize(paperMap.film14x14), QPrinter::Millimeter);
    } else if (film == "14INX17IN") {
        if (paperMap.film14x17 == PS_Defined)
            printer->setPaperSize(QSizeF(paperMap.definedWidth, paperMap.definedHeight),
                                  QPrinter::Millimeter);
        else
            printer->setPaperSize(getPaperSize(paperMap.film14x17), QPrinter::Millimeter);
    } else {
        if (paperMap.filmDefault == PS_Defined)
            printer->setPaperSize(QSizeF(paperMap.definedWidth, paperMap.definedHeight),
                                  QPrinter::Millimeter);
        else
            printer->setPaperSize(getPaperSize(paperMap.filmDefault), QPrinter::Millimeter);
    }
}

void DicomPrintThread::updateJobList()
{
    QDir dir(QString::fromLatin1(dvi->getSpoolFolder()));
    QStringList jobfiles = dir.entryList(QStringList()<<QString("*.job"), QDir::Files);
    foreach (QString jobfile, jobfiles) {
        QFile file(jobfile.prepend(dir.dirName().append('/')));
        if (file.open(QIODevice::ReadOnly)) {
            QTextStream in(&file);
            QString line;
            PrintJob *currentJob = new PrintJob;
            bool terminateFlag = false;
            while (!in.atEnd()) {
                line = in.readLine();
                if (line.startsWith('#')) continue;

                QStringList pair = line.split(' ', QString::SkipEmptyParts);
                bool sizeIs2 = pair.size()==2?true:false;
                if (pair[0] == "copies" && sizeIs2) {
                    bool ok;
                    int copies = pair.at(1).toInt(&ok);
                    if (ok) currentJob->numberOfCopies = copies;
                } else if (pair[0] == "mediumtype" && sizeIs2) {
                    currentJob->mediumType = pair[1];
                } else if (pair[0] == "destination" && sizeIs2) {
                    currentJob->filmDestination = pair[1];
                } else if (pair[0] == "label" && sizeIs2) {
                    currentJob->filmSessionLabel = pair[1];
                } else if (pair[0] == "priority" && sizeIs2) {
                    currentJob->printPriority = pair[1];
                } else if (pair[0] == "owner_id" && sizeIs2) {
                    currentJob->ownerID = pair[1];
                } else if (pair[0] == "study" && sizeIs2) {
                    currentJob->studyUID = pair[1];
                } else if (pair[0] == "series" && sizeIs2) {
                    currentJob->seriesUID = pair[1];
                } else if (pair[0] == "instance" && sizeIs2) {
                    currentJob->instanceUID = pair[1];
                } else if (pair[0] == "terminate") {
                    terminateFlag = true;
                }
            }

            file.close();
            file.remove();

            QString spFile = QString::fromLatin1(dvi->getFilename(currentJob->studyUID.toLatin1(),
                                                                  currentJob->seriesUID.toLatin1(),
                                                                  currentJob->instanceUID.toLatin1()));
            dvi->releaseDatabase();
            if (!(terminateFlag || spFile.isEmpty())) {
                currentJob->storedPrintFilename = spFile;
                printJobList.append(currentJob);
            } else delete currentJob;
        }
    }
}

bool DicomPrintThread::dcm2bmp(QPixmap &pixmap, const char* file)
{
    DicomImage dcmImage(file);
    if (dcmImage.getStatus() != EIS_Normal) {
        return false;
    }

    BITMAPFILEHEADER lpfh;
    memset(&lpfh, 0, sizeof(BITMAPFILEHEADER));
    lpfh.bfType = 0x4d42;  //'B''M'
    lpfh.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);

    BITMAPINFOHEADER lpih;
    memset(&lpih, 0, sizeof(BITMAPINFOHEADER));
    lpih.biSize = sizeof(BITMAPINFOHEADER);
    lpih.biWidth = dcmImage.getWidth();
    lpih.biHeight = dcmImage.getHeight();
    lpih.biCompression = BI_RGB;
    lpih.biBitCount = 24;
    lpih.biPlanes = 1;

    void *pDIB = NULL;

    int size = dcmImage.createWindowsDIB(pDIB, 0, 0, 24, 1, 1);
    if (size == 0) return false;

    lpfh.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + size;

    QByteArray bmp;
    bmp.append((char*)&lpfh, sizeof(BITMAPFILEHEADER));
    bmp.append((char*)&lpih, sizeof(BITMAPINFOHEADER));
    bmp.append((char*)pDIB, size);

    delete pDIB;
    pixmap.loadFromData(bmp);

    return true;
}
